/// 枚举和模式匹配


#![allow(unused)]
fn enums_and_match_1() {
    enum IpAddrKind {
        V4,
        V6,
    }

    // 可以像这样创建 IpAddrKind 两个不同成员的实例
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;


    // 定义一个使用枚举的结构体
    struct IpAddr {
        kind: IpAddrKind,
        address: String,
    }

    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };

    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };

}

// 上面的代码优化改造
// 我们可以使用一种更简洁的方式来表达相同的概念，仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分
fn enums_and_match_2() {
    // 我们直接将数据附加到枚举的每个成员上，这样就不需要一个额外的结构体了。
    // 用枚举替代结构体还有另一个优势：每个成员可以处理不同类型和数量的数据
    enum IpAddr {
        V4(String),
        V6(String),
        V8(u8, u8, u8, u8),
    }

    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));
    let home = IpAddr::V8(127, 0, 0, 1);
}



fn enums_and_match_3() {
    struct Ipv4Addr {
        address: String,
    }

    struct Ipv6Addr {
        address: String,
    }

    // 可以将任意类型的数据放入枚举成员中：例如字符串、数字类型或者结构体。甚至可以包含另一个枚举！
    enum IpAddr {
        V4(Ipv4Addr),
        V6(Ipv6Addr),
    }
}


/// Option 枚举和其相对于空值的优势
// Option 类型应用广泛是因为它编码了一个非常普遍的场景，即一个值要么有值要么没值。
// 从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况，
// 这样就可以避免在其他编程语言中非常常见的 bug。
// Rust 并没有很多其他语言中有的空值功能。空值（Null ）是一个值，它代表没有值。
// 在有空值的语言中，变量总是这两种状态之一：空值和非空值。
fn enums_and_match_4() {
    // Rust 并没有空值，不过它确实拥有一个可以编码存在或不存在概念的枚举
    enum Option<T> {
        Some(T),
        None,
    }
    // 当有一个 Some 值时，我们就知道存在一个值，而这个值保存在 Some 中。当有个 None 值时，
    // 在某种意义上，它跟空值具有相同的意义：并没有一个有效的值。那么，Option<T> 为什么就比空值要好呢？

    // 在对 Option<T> 进行 T 的运算之前必须将其转换为 T。我们可以使用 match 模式匹配来处理
    // 通常这能帮助我们捕获到空值最常见的问题之一：假设某值不为空但实际上为空的情况。
}

/// match 模式匹配
// 为了使用 Option<T> 值，需要编写处理每个成员的代码。你想要一些代码只当拥有 Some(T) 值时运行，
// 允许这些代码使用其中的 T。也希望一些代码在值为 None 时运行，这些代码并没有一个可用的 T 值。
// match 表达式就是这么一个处理枚举的控制流结构：它会根据枚举的成员运行不同的代码，这些代码可以使用匹配到的值中的数据。
fn enums_and_match_5() {
    // Rust 中的匹配是穷举式的（exhaustive）：必须穷举到最后的可能性来使代码有效。
    // 特别的在这个 Option<T> 的例子中，Rust 防止我们忘记明确的处理 None 的情况，
    // 这让我们免于假设拥有一个实际上为空的值，从而使之前提到的价值亿万的错误不可能发生。
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            // 参数名可以随意命名，它代表传递的实际值
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}


/// 通配模式和 _ 占位符
// 我们希望对一些特定的值采取特殊操作，而对其他的值采取默认操作
fn enums_and_match_6() {
    let dice_roll = 9;
    ///  通配模式
    // 即使我们没有列出 u8 所有可能的值，这段代码依然能够编译，因为最后一个模式将匹配所有未被特殊列出的值。
    // 这种通配模式满足了 match 必须被穷尽的要求。
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
    }

    /// _ 占位符
    // Rust 还提供了一个模式，当我们不想使用通配模式获取的值时，请使用 _ ，这是一个特殊的模式，
    // 可以匹配任意值而不绑定到该值。这告诉 Rust 我们不会使用这个值，所以 Rust 也不会警告我们存在未使用的变量。
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => (),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}

}


/// if let 简单控制流
// if let 语法让我们以一种不那么冗长的方式结合 if 和 let，来处理只匹配一个模式的值而忽略其他模式的情况。
fn enums_and_match_7() {
    let some_u8_value = Some(0u8);
    // 使用 match 穷尽匹配
    // 我们想要对 Some(3) 匹配进行操作但是不想处理任何其他 Some<u8> 值或 None 值。
    // 为了满足 match 表达式（穷尽性）的要求，必须在处理完这唯一的成员后加上 _ => ()，这样也要增加很多样板代码。
    match some_u8_value {
        Some(3) => println!("three"),
        _ => (),
    }

    // 我们可以使用 if let 这种更短的方式编写
    // if let 获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match 相同，
    // 这里的表达式对应 match 而模式则对应第一个分支。
    // 使用 if let 意味着编写更少代码，更少的缩进和更少的样板代码。然而，这样会失去 match 强制要求的穷尽性检查。
    // match 和 if let 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
    // 换句话说，可以认为 if let 是 match 的一个语法糖，它当值匹配某一模式时执行代码而忽略所有其他值。
    if let Some(3) = some_u8_value {
        println!("three");
    }

    // 可以在 if let 中包含一个 else。else 块中的代码与 match 表达式中的 _ 分支块中的代码相同，
    // 这样的 match 表达式就等同于 if let 和 else。
    if let Some(3) = some_u8_value {
        println!("three");
    } else {
        println!("three");
    }

}

